
local Map = require("core.Map")
local LuaAoi = _G.lua_aoi
local Msg = require("core.Msg")
local Timer = require("core.Timer")

local Status = require("scene.Status")
local ExcelMap = require("excel.Map")
local ExcelMonster = require("excel.Monster")
local RoleRevive = require("role.RoleRevive")
local SceneEvent = require("core.SceneEvent")
local MapGrid = require("core.MapGrid")
local ObjMonster = require("core.ObjMonster")
local Copy = require("copy.CopyL")
local Buffer = require("buffer.Buffer")
local logic_id = _G.logic_id
--local Util = require("common.Util")
TYPE_HUMAN = 1
TYPE_ITEM = 2
TYPE_MONSTER = 3
TYPE_NPC = 4
TYPE_DISPLAY = 5
TYPE_AOE = 6
TYPE_BUF = 7
TYPE_SKILL = 8
TYPE_TRAP = 9
TYPE_COLLECT = 10
TYPE_MAGIC = 11
TYPE_PET = 12
TYPE_UFO = 13
TYPE_JUMP = 14
TYPE_TOMB = 15

CAMPS_NONE = 0
CAMPS_BAD  = 1
CAMPS_GOOD = 2

objs = objs or {}	--obj_id转换成对象
scenes = scenes or {}	--所有的场景 sceneID->{objId->obj}
scenes_fds = scenes_fds or {}
scenes_9grid = scenes_9grid or {}
obj_max_id = obj_max_id or 0

local q_cap = 131070

GRID_WIDTH = 800
GRID_HEIGHT = 600
local function q_push(id)
    if q_b + 1 == q_f or q_b == q_cap and q_f == 1 then
        assert()
    end
    q[q_b] = id
    q_b = q_b + 1
    if q_b == q_cap + 1 then
        q_b = 1
    end
end

function q_pop()
    if q_f + 1 == q_b or q_f == q_cap and q_b == 1 then
        assert()
    end
    local t = q[q_f]
    q_f = q_f + 1
    if q_f == q_cap + 1 then
        q_f = 1
    end
    return t
end

local function q_init()
    if q then
        return
    end
    q = {}
    q_b = q_cap
    q_f = 1
    for i = 1, q_cap do
        q[i] = (logic_id+1)*100000 + i   -- 场景id中除玩家id生成
    end
end

q_init()


obj_envs = {
    require("core.ObjHuman"),
    require("core.ObjItem"),
    require("core.ObjMonster"),
    require("core.ObjNpc"),
    require("core.ObjDisplay"),
    require("core.ObjAoe"),
    require("core.ObjBuf"),
    require("core.ObjSkill"),
    require("core.ObjTrap"),
    require("core.ObjCollect"),
    require("core.ObjMagic"),
    require("core.ObjPet")
}

local function get_obj_del(obj)
    local mm = Msg.gc.GC_DEL_OBJ
    mm.obj_id = obj.id
    mm.die = obj.die and obj.die or 0
    return mm
end

function get_obj_id_del(obj_id)
    local obj = objs[obj_id]
    local mm = Msg.gc.GC_DEL_OBJ
    mm.obj_id = obj_id
    mm.die = obj.die and obj.die or 0
    return mm
end

function get_obj_id_add(obj_id)
    local obj = objs[obj_id]
    return obj_envs[obj.obj_type].getObjAdd(obj)
end

function getObj(obj_id, obj_uid)
    local obj = objs[obj_id]
    if not obj then
        return
    end
    if obj_uid and obj_uid ~= obj.uid then
        return
    end
    return obj
end

function canSeeEachOther(a, b)
    if a.hide or b.hide or a.scene_id ~= b.scene_id then
        return
    end
    local dx = a.x - b.x
    local dy = a.y - b.y
    local cf = ExcelMap.scene[Map.scene_id_to_map_id[a.scene_id]]
    local GRID_WIDTH = cf.seeWidth
    local GRID_HEIGHT = cf.seeHeight
    return -GRID_WIDTH < dx and dx < GRID_WIDTH and -GRID_HEIGHT < dy and dy < GRID_HEIGHT
end

function canSeeEachOther2(a, b)
    if a.hide or b.hide or a.scene_id ~= b.scene_id then
        return
    else
        return true
    end
end

function create(obj, obj_type, id, uid)
    if not id then
        assert(obj_type ~= TYPE_HUMAN)
        id = q_pop()
    end
    if objs[id] then
        assert()
    end
    objs[id] = obj
    obj.id = id
    obj.obj_type = obj_type
    obj.uid = uid or Timer.now
    return obj
end

function destroy(obj)
    local ret, err = pcall(leaveScene, obj)
    if objs[obj.id] ~= obj then
        assert()
    end
    objs[obj.id] = nil
    if obj.obj_type ~= TYPE_HUMAN then
        q_push(obj.id)
    end
    if not ret then
        assert(nil, err)
    end
end

function destroyAllType(obj)
    if obj_envs[obj.obj_type].destroy then
        obj_envs[obj.obj_type].destroy(obj)
    else
        destroy(obj)
    end
end

function resetPathParam(obj)
    obj.path_len = 0
    obj.path_time = nil
    obj.path_client_x = nil
    obj.path_client_y = nil
end

function checkClientPos(obj, x, y)
    local offset = math.floor(Timer.register_timer.walk * 2 / 1000 * obj.speed) -- 客户端位置校验 如果400心跳内的移动距离内 相信客户端
    local clientx = obj.x
    local clienty = obj.y
    if obj.path_client_x and obj.path_client_y then
        clientx = obj.path_client_x
        clienty = obj.path_client_y
    end

    if ((x - clientx) * (x - clientx) + (y - clienty) * (y - clienty)) < offset * offset then return true end
end

function stop(obj)
    if obj.path_len == 0 then return end
    --print("obj x:", obj.x, " y:", obj.y)
    -- 位置补偿
    local xNew, yNew
    if Timer.last_exe_time.walk then
        xNew, yNew = LuaAoi.pos_fix(obj, Timer.last_exe_time.walk, Timer.now)
    end
    if xNew then
        obj.dir = getDir(xNew - obj.x, yNew - obj.y)
        local mm = Msg.gc.GC_STOP_MOVE
        mm.dir = obj.dir
        jump(obj, xNew, yNew, mm, obj.fd)
    else
        resetPathParam(obj)
        local msgRet = Msg.gc.GC_STOP_MOVE
        msgRet.dir = obj.dir or 4
        msgRet.obj_id = obj.id
        msgRet.x = obj.x
        msgRet.y = obj.y
        sendArea(msgRet, obj.scene_id, obj.x, obj.y, obj.fd)
    end
end

function enterScene(obj, scene_id, x, y, not_fix)
    if obj.scene_id then
        print("ERROR:has enterScene")
        return
    end
    scenes[scene_id] = scenes[scene_id] or {}
    scenes_fds[scene_id] = scenes_fds[scene_id] or {}
    local w, h = Map.getWidthHeight(Map.scene_id_to_map_id[scene_id])
    scenes_9grid[scene_id] = scenes_9grid[scene_id] or MapGrid.new_partition(w, h)

    SceneEvent.create_time[scene_id] = SceneEvent.create_time[scene_id] or Timer.now
    if not not_fix then
        x, y = Map.getFixXY(scene_id, x, y)
    else

        x = math.max(math.min(x, w), 0)
        y = math.max(math.min(y, h), 0)
    end
    if obj.fd and not obj.hide then
        -- 把周围的活动obj告诉这个玩家
        local list = Msg.list
        getAreaObjs(list, scene_id, x, y)
        for k = 1, list[0] do
            Msg.send(getObjAdd(list[k]), obj.fd)
        end
    end

    resetPathParam(obj)
    if obj.fd then
        obj.dir = 4
    end
    obj.scene_id = scene_id
    obj.x = x
    obj.y = y

    -- 把这个对象进入场景的信息告诉别的玩家
    if not obj.hide then
        sendArea(getObjAdd(obj), scene_id, x, y)
    end

    scenes[scene_id][obj.id] = obj
    if obj.fd and not obj.hide then
        scenes_fds[scene_id][obj.fd] = obj
    end

    MapGrid.insert(scenes_9grid[scene_id], obj)
end


-- 取x y附近的所有obj列表
function getAreaObjs(list, scene_id, x, y, except_obj)
    if not scenes[scene_id] then
        list[0] = 0
        return
    end
    local len = 0
    for _, obj in pairs(scenes[scene_id]) do
        if not obj.hide and obj ~= except_obj then
            local dx = obj.x - x
            local dy = obj.y - y
            local cf = ExcelMap.scene[Map.scene_id_to_map_id[scene_id]]
            local GRID_WIDTH = cf.seeWidth
            local GRID_HEIGHT = cf.seeHeight
            if -GRID_WIDTH < dx and dx < GRID_WIDTH and -GRID_HEIGHT < dy and dy < GRID_HEIGHT then
                len = len + 1
                list[len] = obj
            end
        end
    end
    list[0] = len
end

-- 取x y附近的所有同一类型的obj列表
function getAreaObjsByType(list, scene_id, x, y, obj_type)
    if not scenes[scene_id] then
        list[0] = 0
        return
    end
    local len = 0
    for _, obj in pairs(scenes[scene_id]) do
        if not obj.hide and obj.obj_type == obj_type and  getHp(obj) > 0 then
            local dx = obj.x - x
            local dy = obj.y - y
            local cf = ExcelMap.scene[Map.scene_id_to_map_id[scene_id]]
            local GRID_WIDTH = cf.seeWidth
            local GRID_HEIGHT = cf.seeHeight
            if -GRID_WIDTH < dx and dx < GRID_WIDTH and -GRID_HEIGHT < dy and dy < GRID_HEIGHT then
                len = len + 1
                list[len] = obj
            end
        end
    end
    list[0] = len
end


function getDir(dx, dy)
    return math.floor((math.atan2(dy, dx) + math.pi * 2.625) * 4 / math.pi) % 8;
end

local a = {}
local b = {}
local c = {}
function jump(obj, x, y, mm, except_fd, not_fix)
    if obj.x == x and obj.y == y and mm ~= Msg.gc.GC_OBJ_DIE then
        return
    end
    if mm and not not_fix then
        x, y = Map.getFixXY(obj.scene_id, x, y)
    else
        local w, h = Map.getWidthHeight(Map.scene_id_to_map_id[obj.scene_id])
        x = math.max(math.min(x, w), 0)
        y = math.max(math.min(y, h), 0)
    end
    resetPathParam(obj)
    local old_x = obj.x
    local old_y = obj.y
    obj.x = x
    obj.y = y

    local cf = ExcelMap.scene[Map.scene_id_to_map_id[obj.scene_id]]
    local GRID_WIDTH = cf.seeWidth
    local GRID_HEIGHT = cf.seeHeight
    if obj.fd then
        for _, v in pairs(scenes[obj.scene_id]) do
            if not v.hide and v ~= obj then
                local dx = v.x - old_x
                local dy = v.y - old_y
                local aa = -GRID_WIDTH < dx and dx < GRID_WIDTH and -GRID_HEIGHT < dy and dy < GRID_HEIGHT
                local dx = v.x - x
                local dy = v.y - y
                local cc = -GRID_WIDTH < dx and dx < GRID_WIDTH and -GRID_HEIGHT < dy and dy < GRID_HEIGHT
                if aa and not cc then
                    Msg.send(get_obj_del(v), obj.fd)
                elseif not aa and cc then
                    Msg.send(getObjAdd(v), obj.fd)
                end
            end
        end
    end

    a[0] = 0
    b[0] = 0
    c[0] = 0
    if mm and obj.fd and obj.fd ~= except_fd then
        b[0] = 1
        b[1] = obj.fd
    end
    for _, v in pairs(scenes_fds[obj.scene_id]) do
        if v ~= obj then
            local dx = v.x - old_x
            local dy = v.y - old_y
            local aa = -GRID_WIDTH < dx and dx < GRID_WIDTH and -GRID_HEIGHT < dy and dy < GRID_HEIGHT
            local dx = v.x - x
            local dy = v.y - y
            local cc = -GRID_WIDTH < dx and dx < GRID_WIDTH and -GRID_HEIGHT < dy and dy < GRID_HEIGHT
            if aa and not cc then
                a[0] = a[0] + 1
                a[a[0]] = v.fd
            elseif mm and aa and cc then
                b[0] = b[0] + 1
                b[b[0]] = v.fd
            elseif not aa and cc then
                c[0] = c[0] + 1
                c[c[0]] = v.fd
            end
        end
    end
    if 0 < a[0] then
        Msg.sendMulti(get_obj_del(obj), a)
    end
    if 0 < b[0] then
        mm.obj_id = obj.id
        mm.x = obj.x
        mm.y = obj.y
        Msg.sendMulti(mm, b)
    end
    if 0 < c[0] then
        Msg.sendMulti(getObjAdd(obj), c)
    end
end

function leaveScene(obj)
    local scene_id = obj.scene_id
    if not scene_id then
        return
    end
    if not scenes[scene_id][obj.id] then
        assert()
    end
    scenes[scene_id][obj.id] = nil
    MapGrid.remove(scenes_9grid[scene_id], obj)
    if obj.fd then
        scenes_fds[scene_id][obj.fd] = nil
    end
    obj.scene_id = nil
    -- 告诉附近玩家 这个对象离开场景了
    if not obj.hide then
        sendArea(get_obj_del(obj), scene_id, obj.x, obj.y)
    end
    Copy.onLeave(obj, scene_id)
end

function move(obj, x, y, findPath, noFixEnd)
    local xNew, yNew
    if 0 < obj.path_len then
        local dx = obj.path[1] - x
        local dy = obj.path[2] - y
        if -1 < dx and dx < 1 and -1 < dy and dy < 1 then
            return
        end

        if Timer.last_exe_time.walk then
            xNew, yNew = LuaAoi.pos_fix(obj, Timer.last_exe_time.walk, Timer.now)
        end
    end
    if findPath then
        obj.path = obj.path or {}
        local tx, ty
        if 0 < obj.path_len then
            tx = obj.path[1]
            ty = obj.path[2]
        end
        local tempPathLen = Map.getDFSPath(obj.scene_id, obj.path, obj.x, obj.y, x, y)
        if tempPathLen < 1 then
            return
        end
        if 0 < tempPathLen and tx and ty then
            local dx = tx - obj.path[1]
            local dy = ty - obj.path[2]
            if -1 < dx and dx < 1 and -1 < dy and dy < 1 then
                return
            end
        end
        obj.path_len = tempPathLen
    else
        if obj.path_len < 1 then
            local dx = obj.x - x
            local dy = obj.y - y
            if -1 < dx and dx < 1 and -1 < dy and dy < 1 then
                return
            end
        end
        if not noFixEnd then
            x, y = Map.getFixXY(obj.scene_id, x, y)
        else
            local w, h = Map.getWidthHeight(Map.scene_id_to_map_id[obj.scene_id])
            x = math.max(math.min(x, w), 0)
            y = math.max(math.min(y, h), 0)
        end
        obj.path = obj.path or {}
        obj.path[1] = x
        obj.path[2] = y
        if x < 0 then
            assert()
        end
        if y < 0 then
            assert()
        end
        obj.path_len = 1
    end

    if xNew then
        obj.path_client_x = xNew
        obj.path_client_y = yNew
    end
    obj.path_time = Timer.now

    movePath(obj)
end

function movePath(obj)

    local mm = Msg.gc.GC_MOVE
    mm.obj_id = obj.id
    if obj.path_client_x and obj.path_client_y then
        mm.x = obj.path_client_x
        mm.y = obj.path_client_y
    else
        mm.x = obj.x
        mm.y = obj.y
    end
    mm.points[0] = obj.path_len * 2
    for i = 1, obj.path_len do
        local j = obj.path_len + 1 - i
        mm.points[2 * i - 1] = obj.path[2 * j - 1]
        mm.points[2 * i] = obj.path[2 * j]
    end
    sendArea(mm, obj.scene_id, obj.x, obj.y)
    obj.had_stop = nil
end

function sendArea(mm, scene_id, x, y, except_fd)
    if scene_id then
        local cf = ExcelMap.scene[Map.scene_id_to_map_id[scene_id]]
        local GRID_WIDTH = cf.seeWidth
        local GRID_HEIGHT = cf.seeHeight
        LuaAoi.send_area(scenes_fds[scene_id], x, y, except_fd, GRID_WIDTH, GRID_HEIGHT, mm)
    end
end

function sendAreaRadius(mm, scene_id, x, y, radius, except_fd)
    LuaAoi.send_area(scenes_fds[scene_id], x, y, except_fd, radius, radius, mm)
end

function sendGCObjDie(obj, attacker, utilityConfig, hurt_mul, isQuake)
    if obj.obj_type == TYPE_HUMAN then
        obj.db.killerName = attacker and getName(attacker) or ""
        obj.db.killerType = attacker.obj_type == TYPE_MONSTER and 0 or 1
        RoleRevive.sendHumanDie(obj, attacker)
        return
    end

    if not attacker then
        jump(obj, obj.x, obj.y, Msg.gc.GC_OBJ_DIE)
        return
    end

    local r = 10
    if utilityConfig and 0 ~= utilityConfig.is_on_die_back_fly then
        r = 150
        if hurt_mul < 0.5 then

        elseif hurt_mul < 1 then
            r = r * 1.5
        else
            r = r * 2
        end
    end
    if obj.monster_id then
        if isQuake or ExcelMonster.monster[obj.monster_id].die_remain < 2 or ObjMonster.getValueByMonsterType(obj, "can_hit_back_fly")  == 0 then
            r = 0
        end
    end
    local dx = attacker.x - obj.x
    local dy = attacker.y - obj.y
    local len = math.sqrt(dx * dx + dy * dy)
    if len < 1 then
        len = 1
    end
    local x = (obj.x - attacker.x) / len * (r + len) + attacker.x
    local y = (obj.y - attacker.y) / len * (r + len) + attacker.y
    x, y = Map.through(obj.scene_id, obj.x, obj.y, x, y)
    jump(obj, x, y, Msg.gc.GC_OBJ_DIE)
end

function sendScene(mm, scene_id)
    local tb = scenes_fds[scene_id]
    if not tb then
        return
    end
    local list = Msg.list
    local len = 0
    for _, v in pairs(tb) do
        len = len + 1
        list[len] = v.fd
    end
    list[0] = len
    Msg.sendMulti(mm, list)
end

function setSpeed(obj, speed)
--    if obj.obj_type == TYPE_HUMAN then
--        local monster = getObj(obj.db.marry_tour_monster_id, obj.db.marry_tour_monster_uid)
--        if monster then
--            speed = monster.speed + 1
--        end
--    end

    if obj.speed == speed then
        return
    end

    -- 速度改变了之后 要看下当前是否在走 如果在走 那么这里按老速度算出距离 再走剩余的路径
    if obj.path_len and obj.path_len > 0 then
        local xNew, yNew
        if Timer.last_exe_time.walk then
            xNew, yNew, path_len_new = LuaAoi.pos_fix(obj, Timer.last_exe_time.walk, Timer.now)
            obj.path_len = path_len_new
            obj.path_client_x = xNew
            obj.path_client_y = yNew
            obj.path_time = Timer.now

            -- print("set speed:", speed, " walking clientx:", xNew, " clienty:", yNew)
        end
    end

    obj.speed = speed

    if Msg then
        local mm = Msg.gc.GC_SPEED_CHANGE
        mm.obj_id = obj.id
        mm.speed = speed
        sendArea(mm, obj.scene_id, obj.x, obj.y)

        if obj.obj_type == TYPE_HUMAN and obj.hide then
            Msg.send(mm, obj.fd)
        end


        if obj.obj_type == TYPE_HUMAN then
            local pet = getObj(obj.fight_pet_id, obj.fight_pet_uid)
            if pet then
                setSpeed(pet, speed)-- + 16)
            end
        end
    end
end

function walk(during)
    for scene_id, v in pairs(scenes) do
        --[[
        local humanx
        local humany
        local humantime
        for objid, obj in pairs(v) do
        if obj.fd then
        humanx = obj.x
        humany = obj.y
        humantime = obj.path_time
        break
        end
        end
        --]]

        local cf = ExcelMap.scene[Map.scene_id_to_map_id[scene_id]]
        local GRID_WIDTH = cf.seeWidth
        local GRID_HEIGHT = cf.seeHeight

        LuaAoi.walk(get_obj_id_add, get_obj_id_del, v, GRID_WIDTH, GRID_HEIGHT, Timer.now - during, Timer.now)
        -- 飞行停止
        for _, obj in pairs(v) do
            if obj.obj_type == TYPE_HUMAN then
                if obj.status == Status.HUMAN_STATUS_WING_FLY then
                    if obj.path_len < 1 then
                        Status.doHumanControlStatusChange(obj, Status.HUMAN_STATUS_NORMAL)
                        doCalc(obj)
                    end
                end
                --修正x, y
                if obj.x and obj.y then
                    obj.db.x = obj.x
                    obj.db.y = obj.y
                end
            end
            -- 更新9宫格
            MapGrid.update(scenes_9grid[scene_id], obj)
        end

        --[[
        for objid, obj in pairs(v) do
        if obj.fd then
        if humanx ~= obj.x or humany ~= obj.y then
        print("walk x:", obj.x, " y:", obj.y)
        break
        end
        end
        end
        --]]
    end
end

function setHp(obj, hp)
    if not obj_envs[obj.obj_type].setHp then
        assert(nil, obj.obj_type)
    end
    return obj_envs[obj.obj_type].setHp(obj, hp)
end

function getHp(obj)
    if not obj_envs[obj.obj_type].getHp then
        return 0
    end
    return obj_envs[obj.obj_type].getHp(obj)
end

function getHpMax(obj)
    if not obj_envs[obj.obj_type].getHpMax then
        assert(nil, obj.obj_type)
    end
    return obj_envs[obj.obj_type].getHpMax(obj)
end

function getObjAdd(obj)
    return obj_envs[obj.obj_type].getObjAdd(obj)
end

function onDie(obj, killer, utilityConfig, hurt_mul, isQuake, notDyUp)
    return obj_envs[obj.obj_type].onDie(obj, killer, utilityConfig, hurt_mul, isQuakeo, notDyUp)
end

function getBuf(obj)
    if not obj_envs[obj.obj_type].getBuf then
        assert(nil, obj.obj_type)
    end
    return obj_envs[obj.obj_type].getBuf(obj)
end

function setBufLen(obj, len)
    if not obj_envs[obj.obj_type].setBufLen then
        assert(nil, obj.obj_type)
    end
    return obj_envs[obj.obj_type].setBufLen(obj, len)
end

function getAttr(obj, key)
    if not obj_envs[obj.obj_type].getAttr then
        assert(nil, obj.obj_type)
    end
    return obj_envs[obj.obj_type].getAttr(obj, key)
end

function doCalc(obj)
    return obj_envs[obj.obj_type].doCalc(obj)
end

function getLv(obj)
    return obj_envs[obj.obj_type].getLv(obj)
end

function getBodyInfo(obj, realBody)
    if realBody == nil then
        if not obj.bufAttr.awake and Buffer and Buffer.getBodyInfo(obj) then
            return Buffer.getBodyInfo(obj)
        end
    end
    return obj_envs[obj.obj_type].getBodyInfo(obj)
end

function getRadius(obj)
    local getRadius = obj_envs[obj.obj_type].getRadius
    if not getRadius then return 0 end
    return getRadius(obj)
end

function onBeHit(attacker, target, hp_del)
    Status.onBeHit(attacker, target)
    return obj_envs[target.obj_type].onBeHit(attacker, target, hp_del)
end

function onSkill(obj, x, y, skillID)
    local onSkill = obj_envs[obj.obj_type].onSkill
    if onSkill then
        onSkill(obj, x, y, skillID)
    end
end

function getName(obj)
    local callback = obj_envs[obj.obj_type].getName
    return callback and callback(obj) or ""
end

function getOwner(obj)
    if not obj then return end

    local t = obj.owner
    if t then
        return getObj(t.id, t.uid)
    end
end

function isAoe(obj)
    return not obj_envs[obj.obj_type].getAttr
end

-- 取x y附近的所有human列表
function getAreaHumans(list, scene_id, x, y, except_obj)
  if not scenes_fds[scene_id] then
    list[0] = 0
    return
  end
  local len = 0
  for _, obj in pairs(scenes_fds[scene_id]) do
    if not obj.hide and obj ~= except_obj then
      local dx = obj.x - x
      local dy = obj.y - y
      local cf = ExcelMap.scene[Map.scene_id_to_map_id[scene_id]]
      local GRID_WIDTH = cf.seeWidth
      local GRID_HEIGHT = cf.seeHeight
      if -GRID_WIDTH < dx and dx < GRID_WIDTH and -GRID_HEIGHT < dy and dy < GRID_HEIGHT then
        len = len + 1
        list[len] = obj
      end
    end
  end
  list[0] = len
end
